/*
 * @Author: complex 1944755442@qq.com
 * @Date: 2024-01-18 17:44:13
 * @LastEditors: complex 1944755442@qq.com
 * @LastEditTime: 2024-01-23 19:18:12
 * @FilePath: /linuxphoto/HARDWARE/BmpShow.c
 * @Description: 这是默认设置,请设置`customMade`
 */
#include <stdio.h>
//open & read 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
//mmap
#include <sys/mman.h>
//exit
#include <stdlib.h>
//fb
#include <linux/fb.h>
#include "BmpShow.h"
//strcmp
#include <string.h>
//ioctl
#include <sys/ioctl.h>

#define		DEV_PATH	"/dev/fb0"
#define  	USAGE() 	fprintf(stderr, "%s <bmpfile>\n", argv[0])
#define  	MIN(a,b)	((a>b)?(b):(a))
#define  	MAX(a,b)	((a>b)?(a):(b))
#define		argb8888_to_rgb565(color) ({ \
			unsigned int temp = (color); \
			((temp & 0xF80000UL) >> 8) | \
			((temp & 0xFC00UL) >> 5) | \
			((temp & 0xF8UL) >> 3); \
			})

//LCD 屏幕参数
static Lcd_info lcd_info;

static unsigned short rgb_24_to_565(unsigned short r, unsigned short g, unsigned short b)  
{  
	return ((r << 8) & 0xF800) | ((g << 3) & 0x07E0) | ((b >> 3) & 0x001f);
}
//static void show_bmp_core(int mode, int startline, int endline, int start_x, int min_w);
static void show_bmp_core(int fpic, int mode, int startline, int endline, int start_x, int min_w, int pic_w, int pic_h, int byte_depth);

/*
@func:得到bmp文件的相关信息：比如图片宽高、字节深度、数据偏移量.
@attention: 用到了全局变量 fpic、pic_w、pic_h、byte_depth
@param: path:图片的相对路径
@ret：图片的数据偏移量
*/
int lcd_getbmpinfo(char *path, int *fpic, int *pic_w, int *pic_h, int *byte_depth) //现在：通过指针传递数据	//int lcd_getbmpinfo(char *path) //原来：通过全局变量得到数据，
{
	bmp_file_header bmp_fhead;
	bmp_info_header bmp_info;

	//open the file of pic
	//fpic = open(path, O_RDWR);
	*fpic = open(path, O_RDONLY);
	if(*fpic < 0)
	{
		//perror("failed to open the pic file");
		printf("failded to open the pic file:%s\n", path);
		exit(-1);
	}
	//1-------------------------------------------	
	//read the file_header
	//if(read(fpic, &bmp_fhead, sizeof(bmp_fhead) < 0))
	if(read(*fpic, &bmp_fhead, sizeof(bmp_file_header))
		!= sizeof(bmp_file_header))
	{
		perror("read bmp' fileheader failed");
		close(*fpic);
		exit(-1);
	}
	//check whether is BMP file
    if (0 != memcmp(bmp_fhead.type, "BM", 2)) {
        fprintf(stderr, "it's not a BMP file\n");
        close(*fpic);
        return -1;
    }

	//2-------------------------------------------------------
	//read the info_header
	if(read(*fpic, &bmp_info, sizeof(bmp_info_header)) 
		!= sizeof(bmp_info_header))	
	{
		perror("read bmp' infoheader failed");
		close(*fpic);
		exit(-1);
	}

	*pic_w = bmp_info.width;
	*pic_h = bmp_info.height;
	*byte_depth = bmp_info.bpp/8;
	
	#ifdef LCD_DEBUG
	printf("pic's size is %d\n", bmp_fhead.size);	
	printf("picdata's offset is %d\n", bmp_fhead.offset);
	printf("The pic w/h are: %d %d\n", *pic_w, *pic_h);
	printf("The pic's color_depth is %d\n, byte_depth = %d\n", bmp_info.bpp, *byte_depth);
	#endif

	//lseek(fpic, bmp_fhead.offset, SEEK_SET); //将图片位置设置到具体数据区域
	return bmp_fhead.offset;
}

/**
 * @function: show a bmp in lcd 
 * @steps：具体操作:打开图片，读取每一行,再写入lcd每一行的缓存中
 * @param: off---bmp's real data's offset
 * @param: start_x/y bmp in lcd's start position
 * @attention:
*/
void bmp_show(int fpic, int off, int start_x, int start_y, int pic_w, int pic_h, int byte_depth)
{
	int min_w = 0;
	int min_h = 0;
	int wline_size = 0; //表示LCD缓存一行要写的字节量，为较小宽度乘以字节深度

	//检查 start_x start_y
	if(start_x >= lcd_info.lcd_w) start_x = lcd_info.lcd_w;
	if(start_y >= lcd_info.lcd_h) start_y = lcd_info.lcd_h;

	if(lseek(fpic, off, SEEK_SET) < 0)//将图片位置设置到具体数据区域
	{
		perror("lseek failed!");
		exit(-1);
	}
	
	//min_w 是能显示的图片的最小的 x 方向的大小
	min_w = MIN(lcd_info.lcd_w-start_x, pic_w);
	
	printf("min_w = %d\n", min_w);
	lcd_info.lcd_byte_depth = lcd_info.lcd_linelen/lcd_info.lcd_w;  //compute lcd's bytedepth

	//判断此图为正序还是倒序，一般为倒序
	//如果是倒序
	if(pic_h > 0)
	{
		//决定 min_w 和 min_h,从而决定显示的区域（只显示min的范围）
		min_h = MIN(lcd_info.lcd_h-start_y, pic_h);
		if(pic_h > min_h)
		{
			lseek(fpic,pic_w*byte_depth*(pic_h-min_h),SEEK_CUR);
		}
		show_bmp_core(fpic, 0, start_y+min_h-1, start_y, start_x, min_w, pic_w, pic_h, byte_depth);
	}
	else //pic_h < 0,此图为正序
	{
		pic_h = -pic_h;
		min_h = MIN(lcd_info.lcd_h-start_y, pic_h);
		show_bmp_core(fpic, 1, start_y, start_y+min_h-1, start_x, min_w, pic_w, pic_h, byte_depth);
	}
}

/*
*@func:打开lcd 驱动文件，并且将其内存进行映射
*/
int lcd_init(void)
{
	struct fb_var_screeninfo MyVar = {0};
	struct fb_fix_screeninfo MyFix = {0};
	
	//open the fb's dev
	int fd = open(DEV_PATH, O_RDWR);
	if(fd < 0)
	{
		perror("failed to open fd dev");
		exit(-1);
	}
	
	//ioctl the fb to get the device's info 
	if( ioctl(fd, FBIOGET_VSCREENINFO, &MyVar) < 0)
	{
		perror("ioctl the var dat error");
		exit(-1);
	}
	if( ioctl(fd, FBIOGET_FSCREENINFO, &MyFix) < 0)
	{
		perror("ioctl the fix dat error");
		exit(-1);
	}
	lcd_info.lcd_w = MyVar.xres;
	lcd_info.lcd_h = MyVar.yres;
	lcd_info.lcd_linelen = MyFix.line_length;
	lcd_info.totallen = lcd_info.lcd_linelen*lcd_info.lcd_h;//冗余参数
	
	//printf("LCD'w/h = %d %d\nlinelen = %d\n", lcd_info.lcd_w, lcd_info.lcd_h, lcd_info.lcd_linelen);
	//YEHUO:W/H/BD = 800/480/2 已经定义为宏


	//fb 的内存映射 mmap()
	lcd_info.lcd_buf = mmap(NULL, lcd_info.totallen, PROT_WRITE, MAP_SHARED, fd, 0);
	if (MAP_FAILED == (void *)lcd_info.lcd_buf) 
	{ 
		perror("mmap error"); 
		close(fd); 
		exit(EXIT_FAILURE); 
	}
/*
	//得到图片信息，清空屏幕（白屏）
	off = lcd_getbmpinfo(argv[1]);
	memset(lcd_buf, 0xff, totallen);

*/
	return 0;
}


/*
@param: mode = 0 倒序， mode=1 正序
@param: startline:LCD起始行:倒序为start_y+min_h-1，正序为start_y
@param: endline: LCD结束行：倒序为start_y,正序为start_y+min_h-1
@param: start_x, min_w 为bmp_show 中的参数
*/
static void show_bmp_core(int fpic, int mode, int startline, int endline, int start_x, int min_w,  int pic_w, int pic_h, int byte_depth)
{
	unsigned char *line_buf = malloc(pic_w*byte_depth);
	
	if(line_buf == NULL)
	{
		perror("malloc failed!\n");
		exit(-1);
	}
	
	//如果为倒序，那么第一个字节写到fb的最后一行的第一个字节
	//即 bmp 文件正序走，但 lcd_buf 倒着走
	//S1: 让LCD缓存的基地址定位在 min_h 所在的行
	int overendline = 0;
	if(mode == 0)
	{
		overendline = endline-1;
	}
	else
	{
		overendline = endline+1;
	}

	for(int cline = startline; cline !=overendline;	 )
	{
		memset(line_buf, 0, sizeof(line_buf));
		//S2: 读bmp一行的数据，读取的字节数为pic_w*byte_depth(line_buf 空间要足够，不要发生越界)
		if(pic_w*byte_depth != read(fpic, line_buf, pic_w*byte_depth))
		{
			perror("read failed!");
			exit(-1);
		}
		char div4_mod = (pic_w * byte_depth)%4;
		if(div4_mod!=0)//如果图片的字节不能被4整除，windows系统会填补垃圾字节，因此这里跳过垃圾字节
		{
			lseek(fpic, div4_mod, SEEK_CUR);
		}

		//S3: 将 min_w 的数据写入LCD缓存中,写入的字节数为 wline_size
		if(byte_depth == 2)
		{
			memcpy(&lcd_info.lcd_buf[cline*lcd_info.lcd_w+start_x],line_buf, min_w*lcd_info.lcd_byte_depth);
		}
		else if(byte_depth == 3)
		{
			for(int ccol = 0; ccol < min_w; ccol++)//一行一共 min_w 个像素点
			{
				//2. 将转换成的16位数据，写入 lcd_buf 当中去
				lcd_info.lcd_buf[cline*lcd_info.lcd_w+start_x+ccol] = rgb_24_to_565(line_buf[ccol*3+2], line_buf[ccol*3+1], line_buf[ccol*3+0]);  //RGB
			}
		}
		//S4: 更新bmp的一行，更新LCD的一行，以此类推
		if(mode == 0)
		{
			cline--;
		}
		else
		{
			cline++;
		}
	}
	free(line_buf);
	line_buf = NULL;
}



/**
 * @func: 供用户调用的显示bmp的API
 * @param: str--bmp file's name
 * @param: flag--decide whether to refresh the whole lcd
 * if flag=1, refreash the whole lcd
*/
void lcd_showbmp_api(char *filename, int start_x, int start_y, int flag)
{
	//0. 将lcd_buf 清空
	int off = 0;	
	if(flag == 1) //if flag=1, refreash the whole lcd
	{
		memset(lcd_info.lcd_buf, 0xff, lcd_info.totallen);
	}
	
	//1. 获取图片的头信息、文件信息：fpic,pic_w, pic_h,byte_depth, off 用于传给bmp_show()
	int pic_w = 0;
	int pic_h = 0;
	int byte_depth = 0;
	int fpic = -1;
	off = lcd_getbmpinfo(filename, &fpic, &pic_w, &pic_h, &byte_depth);

	//2. 显示图片
	bmp_show(fpic, off, start_x, start_y, pic_w, pic_h, byte_depth);
	//核心调用show_bmp_core(int fpic, int mode, int startline, int endline, int start_x, int min_w, int pic_w, int pic_h, int byte_depth);
}

//以上的解决方案是：将图片一边读出数据，然后将数据再写入屏幕。如果有n行，需要重复n次。
//以下为另外一种解决方案：首先一次性将图片的数据全部读取出来，然后一次性写入屏幕。


/*
@func:得到bmp文件的相关信息：比如图片宽高、字节深度、数据偏移量.
@attention: 用到了全局变量 fpic、pic_w、pic_h、byte_depth
@param: path:图片的相对路径
@ret：状态码：正常为0
*/
static int lcd_getbmpinfo_withbuf(char *path, char *buf, int *pic_w, int *pic_h, int *byte_depth) //现在：通过指针传递数据	//int lcd_getbmpinfo(char *path) //原来：通过全局变量得到数据，
{
	bmp_file_header bmp_fhead;
	bmp_info_header bmp_info;

	//open the file of pic
	//fpic = open(path, O_RDWR);
	int fpic = open(path, O_RDONLY);
	if(fpic < 0)
	{
		//perror("failed to open the pic file");
		printf("failded to open the pic file:%s\n", path);
		exit(-1);
	}
	//1-------------------------------------------	
	//read the file_header
	//if(read(fpic, &bmp_fhead, sizeof(bmp_fhead) < 0))
	if(read(fpic, &bmp_fhead, sizeof(bmp_file_header))
		!= sizeof(bmp_file_header))
	{
		perror("read bmp' fileheader failed");
		close(fpic);
		exit(-1);
	}
	//check whether is BMP file
    if (0 != memcmp(bmp_fhead.type, "BM", 2)) {
        fprintf(stderr, "it's not a BMP file\n");
        close(fpic);
        return -1;
    }

	//2-------------------------------------------------------
	//read the info_header
	if(read(fpic, &bmp_info, sizeof(bmp_info_header)) 
		!= sizeof(bmp_info_header))	
	{
		perror("read bmp' infoheader failed");
		close(fpic);
		exit(-1);
	}

	*pic_w = bmp_info.width;
	*pic_h = bmp_info.height;
	*byte_depth = bmp_info.bpp/8;
	
	if(((*pic_w)>YEHUO_LCD_W) || ((*pic_h)>YEHUO_LCD_H))
	{
		perror("bmp size is too big!!");
		close(fpic);
		exit(-1);
	}
#if 0 // 存在垃圾数据
	//这里：将图片的所有数据一次性读出
	lseek(fpic, bmp_fhead.offset, SEEK_SET); //将图片位置设置到具体数据区域
#endif

	if(*pic_w%4==0){
		//被4整除，一下子读完
		read(fpic, buf, (*pic_w)*(*pic_h)*(*byte_depth));
	}else{
		//不被4整除，一行一行读
		//每行读到最后（width），设置当前往后偏移（宽%4的余数）
		for(int i=0;i<(*pic_h);i++)
		{
			read(fpic,buf+i*(*pic_w)*(*byte_depth),(*pic_w)*(*byte_depth));
			lseek(fpic,(*pic_w%4),SEEK_CUR); //跳过垃圾数据
		}
	}

	if(-1 == read(fpic, buf, (*pic_w)*(*pic_h)*(*byte_depth)))//将图片的数据全部读到buf当中
	{
		//read()如果读到文件末尾，返回0；如果出现错误，返回-1
		perror("read bmp' colordata failed");
		exit(-1);
	}
	close(fpic);
	return 0;
}



/*
*@func:图片数据处理函数:将bmp的格式适应为屏幕的格式(屏幕为RGB565格式)
*@输入:bufIn 已经得到的bmp数据， width/height/byte_depth 图片的高、宽、字节深度
*@输出：bufOut 实际要写入lcd_buf的数据
*/
static int process_bmp(char* bufIn,__uint16_t* bufOut,int width,int height, int byte_depth)
{
    if(bufIn==NULL)
    {
        printf("bufIn is NULL\n");
        return -1;
    }
#if 0
    for(int i=0;i<height*width;i++) //将24位bmp图片数据变成800*480*4个字节可在lcd中显示的数据
    {
        bufOut[i] = 0x00<<24|bufIn[3*i+0]|bufIn[3*i+1]<<8|bufIn[3*i+2]<<16;
    }
#endif
	
	switch (byte_depth)
	{
		case 3://若bmp图片为24位，将24位bmp图片数据变成800*480*2个字节可在lcd中显示的数据
			for(int i=0;i<height*width;i++) //将24位bmp图片数据变成800*480*4个字节可在lcd中显示的数据
			{
				bufOut[i] = rgb_24_to_565(bufIn[i*3+2], bufIn[i*3+1], bufIn[i*3+0]);
			}
			break;
		case 2://若bmp图片为16位，不进行转变
			for(int i=0;i<height*width;i++) 
			{	
				__uint16_t tempbufIn =bufIn[2*i+1] << 8;
				bufOut[i] = tempbufIn | (bufIn[2*i]);
			}
			break;
		default:
			fprintf(stderr, "Bmp type Cannot Support! bmp's byteDepth is:%d\n", byte_depth);
			exit(-1);
			break;
	}

	//将图片关于x轴镜像
    for(int j=0;j<height/2;j++)
	{
        for(int i=0;i<width;i++) 
        {
            bufOut[j*width+i] ^= bufOut[width*(height-1-j)+i];
            bufOut[width*(height-1-j)+i] ^= bufOut[j*width+i];
            bufOut[j*width+i] ^= bufOut[width*(height-1-j)+i];
        }
    }
    return 0;
}


/**
 * @brief 图片展示函数，可在满足屏幕分布规则的情况下任意位置放置不同大小的图片
 * 
 * @param bmp_buf 所显示图片的数据
 * @param loca_x  图片左上角所放置的坐标x
 * @param loca_y  图片左上角所放置的坐标y
 * @param width   图片宽度 单位字
 * @param height  图片高度 单位字
 * @return int  显示成功返回0
 */
static int bmp_show_withcheck_core(__uint16_t* display_buf,int loca_x,int loca_y,int width,int height)
{
	//open the fb's dev
	int fd = open(DEV_PATH, O_RDWR);
	if(fd < 0)
	{
		perror("failed to open fd dev");
		exit(-1);
	}
	//fb 的内存映射 mmap()
    __uint16_t* m_lcd = mmap(NULL,YEHUO_LCD_TOTALLEN,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
	if (MAP_FAILED == (void *)m_lcd) 
	{ 
		perror("mmap error"); 
		close(fd); 
		exit(EXIT_FAILURE); 
	}

	memset(m_lcd, 0xff, YEHUO_LCD_TOTALLEN);//屏幕清白

	//检查参数：要求图片能够全部显示出来
    if((loca_x+width)>YEHUO_LCD_W||(loca_y+height)>YEHUO_LCD_H)
    {
        printf("picture size too big to loca in here\n");
        munmap(m_lcd,YEHUO_LCD_TOTALLEN);
        close(fd);
        exit(-1); 
    }
    if(loca_x<0||loca_y<0)
    {
        printf("picture size too litle to loca in here\n");
        munmap(m_lcd,YEHUO_LCD_TOTALLEN);
        close(fd);
        exit(-1); 
    }

	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			//m_lcd[loca_y*YEHUO_LCD_W+loca_x+i*YEHUO_LCD_W+j] = bmp_buf[i*width+j];
			m_lcd[i*YEHUO_LCD_W+j] = display_buf[i*width+j];
		}
	}
    munmap(m_lcd,YEHUO_LCD_TOTALLEN);
    close(fd);
    return 0;
}

/**
 * @brief 图片显示函数，带抠图功能
 * 
 * @param bmpName 图片路径名
 * @param loca_x  图片所在lcd屏幕中左上角坐标x
 * @param loca_y  图片所在lcd屏幕中左上角坐标y
 * @param shape   矩形直接显示：DISPLAY_ALL 抠图只显示图案：DISPLAY_PATTERN
 */
void lcd_showbmp_withcheck_api(char* bmpName,int loca_x,int loca_y)
{
    //char bmp_buf[YEHUO_LCD_TOTALLEN] = {0}; //第一阶段存储图片元素数据
	//获取图片的信息
	char bmp_buf[YEHUO_LCD_W*YEHUO_LCD_H*3] = {0};//最多允许bmpRGB888=24位(3字节)的数据
    int pic_w = 0; //图片宽度
    int pic_h = 0;//图片高度
	int byte_depth = 0;//图片字节深度
    lcd_getbmpinfo_withbuf(bmpName,(char *)bmp_buf,&pic_w,&pic_h, &byte_depth);    

#if 1
	printf("The pic w/h are: %d %d\n", pic_w, pic_h);
	printf("The pic's byte_depth = %d\n", byte_depth);
#endif

	__uint16_t display_buf[YEHUO_LCD_W*YEHUO_LCD_H] = {0}; //待显示的图片数据     
	printf("Now process bmp\n");     
    process_bmp((char *)bmp_buf,(__uint16_t *)display_buf,pic_w,pic_h, byte_depth);//出现段错误

#if 1
    bmp_show_withcheck_core(display_buf,loca_x,loca_y,pic_w,pic_h);
#endif
}


